package mamba

import (
	"context"
	"errors"
	"fmt"
	"net"

	_ "embed"
	http "github.com/Danny-Dasilva/fhttp"
	http2 "github.com/Danny-Dasilva/fhttp/http2"
	utls "github.com/Danny-Dasilva/utls"
	"golang.org/x/net/proxy"
	"strings"
	"sync"
)

//go:embed amazon_ips
var ips string
//var rdb *redis.Client
//var redisCtx = context.Background()
//func init() {
//	rdb = redis.NewClient(&redis.Options{
//		Addr:     "localhost:6379",
//		Password: "", // no password set
//		DB:       0,  // use default DB
//	})
//}

var errProtocolNegotiated = errors.New("protocol negotiated")
type RoundTripper struct {
	sync.Mutex
	// fix typing
	JA3       string
	UserAgent string

	Cookies           []Cookie
	cachedConnections map[string]net.Conn
	cachedTransports  map[string]http.RoundTripper

	Dialer proxy.ContextDialer
}

func (rt *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	// Fix this later for proper cookie parsing
	//for _, properties := range rt.Cookies {
	//	req.AddCookie(&http.Cookie{
	//		Name: properties.Name,
	//		Value:      properties.Value,
	//		Path:       properties.Path,
	//		Domain:     properties.Domain,
	//		Expires:    properties.JSONExpires.Time, //TODO: scuffed af
	//		RawExpires: properties.RawExpires,
	//		MaxAge:     properties.MaxAge,
	//		HttpOnly:   properties.HTTPOnly,
	//		Secure:     properties.Secure,
	//		Raw:        properties.Raw,
	//		Unparsed:   properties.Unparsed,
	//	})
	//}
	req.Header.Set("User-Agent", rt.UserAgent)
	addr := rt.getDialTLSAddr(req)
	if _, ok := rt.cachedTransports[addr]; !ok {
		if err := rt.getTransport(req, addr); err != nil {
			return nil, err
		}
	}
	return rt.cachedTransports[addr].RoundTrip(req)
}

func (rt *RoundTripper) getTransport(req *http.Request, addr string) error {
	switch strings.ToLower(req.URL.Scheme) {
	case "http":
		rt.cachedTransports[addr] = &http.Transport{DialContext: rt.Dialer.DialContext, DisableKeepAlives: true}
		return nil
	case "https":
	default:
		return fmt.Errorf("invalid URL scheme: [%v]", req.URL.Scheme)
	}

	_, err := rt.dialTLS(context.Background(), "tcp", addr)


	switch err {
	case errProtocolNegotiated:
	case nil:
		// Should never happen.
		panic("此域名从未单线程请求过的，先请求一次主域名，然后再并发请求此域名下的其他链接，不能直接并发请求")
		//panic("dialTLS returned no error when determining cachedTransports")
	default:
		return err
	}


	return nil
}

func (rt *RoundTripper) dialTLS(ctx context.Context, network, addr string) (net.Conn, error) {
	rt.Lock()
	defer rt.Unlock()

	// If we have the connection from when we determined the HTTPS
	// cachedTransports to use, return that.
	if conn := rt.cachedConnections[addr]; conn != nil {
		delete(rt.cachedConnections, addr)
		return conn, nil
	}

	var rawConn net.Conn
	var err error
	if strings.Contains(addr, "www.amazon") {
		//ips := []string{
		//	//"65.8.169.144",
		//	//"13.227.2.197",   // 13,227,2-239,1-254
		//	//"99.84.238.254",// 99,84,2-239,1-254
		//	//"143.204.135.75",// 143,204,2-239,1-254
		//	//"65.8.170.2",// 65,8,2-239,1-254
		//	//"143.204.128.200",// 143,204,2-239,1-254
		//	//"52.85.130.188",// 143,204,2-239,1-254
		//	//"54.192.193.149",// 143,204,2-239,1-254
		//	//"99.84.104.115",// 143,204,2-239,1-254
		//	"104.87.85.211",// akamai.com
		//}

		//ipNetwork := []string{
		//	"65.8",
		//	//"13.227",
		//	//"99.84",
		//	//"143.204",
		//	//"52.85",
		//	//"52.46",
		//	//"54.192",
		//}

		ipNetwork := strings.Split(ips, "\r\n")

		randomIpNetwork := Choice(ipNetwork)
		connectIp := fmt.Sprintf("%s.%d:443", randomIpNetwork, RandomInt(1, 254))
		//connectIp := fmt.Sprintf("%s.%d.%d:443", randomIpNetwork, aa, RandomInt(1, 254))
		//connectIp = "52.85.31.202:443"
		//connectIp = "52.222.231.5:443"
		//connectIp = fmt.Sprintf("%s.%d:443", rdb.LPop(redisCtx, "ips").Val(), RandomInt(1, 254))
		fmt.Println(connectIp)

		rawConn, err = rt.Dialer.DialContext(ctx, network, connectIp)
	} else if strings.Contains(addr, "account-status.amazon") {

		ipNetwork := []string{
			"52.119.170.35",
			"52.119.162.242",
			"52.119.166.42",
			"52.46.131.103",
			"52.46.140.227",
			"52.95.119.6",
			"52.94.243.42",
			"52.95.115.72",
			"52.95.122.142",

		}

		randomIpNetwork := Choice(ipNetwork)
		connectIp := fmt.Sprintf("%s:443", randomIpNetwork)
		rawConn, err = rt.Dialer.DialContext(ctx, network, connectIp)
	} else {
		rawConn, err = rt.Dialer.DialContext(ctx, network, addr)
	}

	if err != nil {
		return nil, err
	}

	var host string
	if host, _, err = net.SplitHostPort(addr); err != nil {
		host = addr
	}
	//////////////////

	spec, err := StringToSpec(rt.JA3, rt.UserAgent)
	if err != nil {
		return nil, err
	}

	conn := utls.UClient(rawConn, &utls.Config{ServerName: host, InsecureSkipVerify: true}, // MinVersion:         tls.VersionTLS10,
		// MaxVersion:         tls.VersionTLS13,

		utls.HelloCustom)

	if err := conn.ApplyPreset(spec); err != nil {
		return nil, err
	}

	if err = conn.Handshake(); err != nil {
		_ = conn.Close()

		if err.Error() == "tls: CurvePreferences includes unsupported curve" {
			//fix this
			return nil, fmt.Errorf("conn.Handshake() error for tls 1.3 (please retry request): %+v", err)
		}

		return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
	}

	//////////
	if rt.cachedTransports[addr] != nil {
		return conn, nil
	}

	// No http.Transport constructed yet, create one based on the results
	// of ALPN.
	switch conn.ConnectionState().NegotiatedProtocol {
	case http2.NextProtoTLS:
		parsedUserAgent := parseUserAgent(rt.UserAgent)
		t2 := http2.Transport{DialTLS: rt.dialTLSHTTP2,
			PushHandler: &http2.DefaultPushHandler{},
			Navigator:   parsedUserAgent,
		}
		rt.cachedTransports[addr] = &t2
	default:
		// Assume the remote peer is speaking HTTP 1.x + TLS.
		rt.cachedTransports[addr] = &http.Transport{DialTLSContext: rt.dialTLS}

	}

	// Stash the connection just established for use servicing the
	// actual request (should be near-immediate).
	rt.cachedConnections[addr] = conn

	return nil, errProtocolNegotiated
}

func (rt *RoundTripper) dialTLSHTTP2(network, addr string, _ *utls.Config) (net.Conn, error) {
	return rt.dialTLS(context.Background(), network, addr)
}

func (rt *RoundTripper) getDialTLSAddr(req *http.Request) string {
	host, port, err := net.SplitHostPort(req.URL.Host)
	if err == nil {
		return net.JoinHostPort(host, port)
	}
	return net.JoinHostPort(req.URL.Host, "443") // we can assume port is 443 at this point
}

func newRoundTripper(ja3, ua string, Dialer ...proxy.ContextDialer) http.RoundTripper {
	//ja3 := `771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-34-51-43-13-45-28,29-23-24-25-256-257,0`
	//ua := `Mozilla/5.0（Windows NT 10.0；Win64；x64；rv:101.0）Gecko/20100101 Firefox/101.0`

	//ja3 := `771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0`
	//ua := `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36`
	if len(Dialer) > 0 {
		return &RoundTripper{
			Dialer: Dialer[0],

			JA3:               ja3,
			UserAgent:         ua,
			Cookies:           nil,
			cachedTransports:  make(map[string]http.RoundTripper),
			cachedConnections: make(map[string]net.Conn),
		}
	}

	return &RoundTripper{
		Dialer:            proxy.Direct,
		JA3:               ja3,
		UserAgent:         ua,
		Cookies:           nil,
		cachedTransports:  make(map[string]http.RoundTripper),
		cachedConnections: make(map[string]net.Conn),
	}
}